//Copyright 2011 - Jonathan Allen

using System;
using System.Linq;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;

namespace Granite.DataAnnotations
{
    /// <summary>
    /// Apply this attribute to indicate which properties affect this calculated property. 
    /// Classes that inherit from ModelBase will use this information to automatically throw the appropriate property changed events.
    /// </summary>
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
    [Pure]
    public sealed class CalculatedFieldAttribute : Attribute
    {
        readonly ReadOnlyCollection<string> m_SourceProperties;

        readonly string m_Sources;

        /// <summary>
        /// Apply this attribute to indicate which properties affect this calculated property. 
        /// Classes that inherit from ModelBase will use this information to automatically throw the appropriate property changed events.
        /// </summary>
        /// <param name="sources">Comma separated list of properties that, when changed, invalidate this property. Whitespace will be trimmed.</param>
        [Pure]
        public CalculatedFieldAttribute(string sources)
        {
            if (sources == null)
                throw new ArgumentNullException("sources", "sources is null");
            if (String.IsNullOrEmpty(sources))
                throw new ArgumentException("sources is null or empty.", "sources");
            Contract.EndContractBlock();

            m_Sources = sources;
            m_SourceProperties = new ReadOnlyCollection<string>((from s in sources.Split(',') select s.Trim()).ToArray());
        }

        /// <summary>
        /// List of properties that, when changed, invalidate this property
        /// </summary>
        public ReadOnlyCollection<string> SourceProperties
        {
            get
            {
                Contract.Ensures(Contract.Result<ReadOnlyCollection<string>>() != null);
                Contract.Ensures(Contract.ForAll(Contract.Result<ReadOnlyCollection<string>>(), p => p != null));

                return m_SourceProperties;
            }
        }

        /// <summary>
        /// Comma separated list of properties that, when changed, invalidate this property
        /// </summary>
        public string Sources
        {
            get
            {
                Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>()));

                return m_Sources;
            }
        }

        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic"), SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        [ContractInvariantMethod]
        void ObjectInvariant()
        {
			Contract.Invariant(!string.IsNullOrEmpty(m_Sources));
			Contract.Invariant(m_SourceProperties != null);
            Contract.Invariant(Contract.ForAll(m_SourceProperties, p => p != null));
        }
    }
}
